#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "CodeTextView.h"       // cpp: max nesting level
#include "SrcBrowser.h"

#include "Class.h"
#include "Menu.h"
#include "Box.h"
#include "TextFormatter.h"
#include "OrdColl.h"
#include "Scroller.h"
#include "WindowSystem.h"
#include "GotoDialog.h"
#include "ClassManager.h"
#include "MenuBar.h"
#include "Env.h"
#include "FileData.h"
#include "FindChange_e.h"
#include "Alert_e.h"
#include "Math.h"
#include "Fields.h"
#include "VObjectText.h"
#include "String.h"

#include "EtProgEnv.h"
#include "ClassList.h"
#include "MethodB.h"
#include "EtPeCmdNo.h"
#include "EtPeManager.h"
#include "EtPeTool.h"

//---- SourceBrowser -----------------------------------------------------------

NewMetaImpl(PeSourceBrowser, Document, (TP(textview), TP(listview), TP(text),
						TP(ccl), TP(log), TB(isCCode)));

int PeSourceBrowser::srcCount;
PeSourceBrowser *PeSourceBrowser::browser;

PeSourceBrowser::PeSourceBrowser(Manager *m) : Document(cDocTypeAscii)
{
	m->AddManager(this);
	Font *fd= gFixedFont;
	text= new VObjectText(256, fd);
	text->SetDefTab(fd->Width(' ') * Env::GetValue("CodeText.TabPos", 8));

	ccl= 0;
	definition= FALSE;
	log= new OrdCollection;
	isCCode= FALSE;
	base= (srcCount++ == 0);
}

PeSourceBrowser::~PeSourceBrowser()
{
	SafeDelete(textview);
	SafeDelete(listview);
	SafeDelete(text);
	SafeDelete(log);
	SafeDelete(methods);
	SafeDelete(implementors);
}

VObject *PeSourceBrowser::DoMakeContent()
{
	textview= new PeSrcBrowserTextView(this, Rectangle(Point(2000,cFit)), text);
	listview= new PeClassListView(this);
	methods= new PeMethodBrowser(this);
	implementors= new PeImplementors(this);
	SetFirstHandler(textview);
	classScroller= new Scroller(listview, Point(-1, 100));
	classScroller->SetFlag(eVObjHFixed);

	methodScroller= new Scroller(methods);

	implTitle= new TextField(cIdNone, 20, gFixedFont->WithFace(eFaceItalic));
	implTitle->SetEditable(FALSE);

	return
		new VBox(
			new HBox(
				classScroller,
				methodScroller,
				new SplitHandle,
				new VBox(gPoint2, (VObjAlign)(eVObjVExpand|eVObjHExpand),
					implTitle,
					new Scroller(implementors),
					0
				),
				0
			),
			new SplitHandle,
			new Scroller(textview, Point(550, 300)),
			0
		);
}

void PeSourceBrowser::InputKbd(Token &t)
{
	if (EtPeTool::WantGrabKeyToken(t, classScroller))
		classScroller->Input(t.Pos, t, GetWindow());
	else if (EtPeTool::WantGrabKeyToken(t, methodScroller))
		methodScroller->Input(t.Pos, t, GetWindow());
	else
		Document::InputKbd(t);
}

bool PeSourceBrowser::LoadData(Data *d, bool unique)
{
	bool ok= Document::LoadData(d, unique);
	if (ok) {
		ccl= 0;
		listview->SelectClass(ccl);
		methods->ShowMethodsOf(ccl);
	}
	return ok;
}

void PeSourceBrowser::SetWindowTitle(const char *name)
{
	if (base)
		GetWindow()->SetTitle(form("*** %s ***", name));
	else
		Document::SetWindowTitle(name);
}

bool PeSourceBrowser::CanImport(Data *data)
{
	if (data)
		return data->CanConvert(Meta(Text));
	return TRUE;
}

Command *PeSourceBrowser::DoImport(Data *data)
{
	Text *t;
	if (t= (Text*) data->AsObject(Meta(Text))) {
		Command *cmd= textview->InsertText(t);
		SafeDelete(t);
		return cmd;
	}
	return gNoChanges;
}

extern bool ReadFromAsPureTextWithMarks(IStream &s, long sizehint, StyledText *tx);
extern bool PrintOnAsPureTextWithMarks(OStream &s, StyledText *tx);

bool PeSourceBrowser::DoWrite(OStream &s, Data*)
{
	return PrintOnAsPureTextWithMarks(s, text);
}

bool PeSourceBrowser::DoReadData(Data *data)
{
	IStream s(data->GetStreamBufForReading());
	int rc= ReadFromAsPureTextWithMarks(s, data->SizeHint(), text);
	if (isCCode= data->Type() == cDocTypeCPlusPlus)
		textview->FormatCode();
	else
		textview->SetDefaultStyle();
	textview->SetText(text);
	return rc;
}

void PeSourceBrowser::RevealAndSelectLine(int l)
{
	LineMark *lm= textview->MarkAtLine(Math::Range(0, textview->Lines()-1, l-1));
	textview->SetSelection(lm->Pos(),lm->End());
	Rectangle r= textview->SelectionRect();
	textview->Scroll(cPartScrollAbs, Point(0,r.origin.y)+Point(0,2));
}

void PeSourceBrowser::Log(Class *cl)
{
	if (cl != ccl)
		log->Add(cl);
}

void PeSourceBrowser::Control(int id, int what, void *val)
{
	PeMethodReference *mr= (PeMethodReference *)val;

	switch (what) {
	case cPeCLChangedClass:
		SetClass((Class*)val, definition);
		break;
		
	case cPeChangedMethod:
		RevealMethod(mr);
		break;
		
	case cPeImplementors:
		{
			GrShowWaitCursor wc;
			implTitle->SetFString(TRUE, "implementors of: %s", mr->Str());
			implementors->ShowImplementorsOf(mr, FALSE);
		}
		break;
		
	case cPeInherited:
		implTitle->SetFString(TRUE, "inherited: %s", mr->Str());
		implementors->ShowInherited(mr);
		break;
		
	case cPeOverrides:
		implTitle->SetFString(TRUE, "overrides of: %s", mr->Str());
		implementors->ShowImplementorsOf(mr, TRUE);
		break;
		
	default:
		Document::Control(id, what, val);
	}
}

void PeSourceBrowser::SetClass(Class *cl, bool what, bool reveal)
{
	Log(cl);
	DoSetClass(cl, what, TRUE, reveal);
}

void PeSourceBrowser::RevealMethod(PeMethodReference *mr)
{
	SetClass(mr->GetClass(), FALSE, FALSE);
	textview->ShowMethod(mr);
	methods->SelectMethod(mr);
}

void PeSourceBrowser::DoSetClass(Class *cl, bool decl, bool unique, bool reveal)
{
	char fname[1000];
	bool sameClass= (cl == ccl && decl == definition);
	bool sameFile= FALSE;

	if (!sameClass) {
		if (!gEtPeManager->FileOfClass(cl, fname, decl)) {
			ShowAlert(eAlertNote, "Can't find file @B%s@P in Env::SrcPath\n",
							BaseName(fname));
			return;
		}

		FileData *fd= new FileData(fname);
		if (fd->UniqueId() != UniqueId()) {
			if (! LoadData(fd, unique)) {
				listview->SelectClass(ccl);
				return;
			}
		} else {
			sameFile= TRUE;
			delete fd;
		}
		definition= decl;
		ccl= cl;
		listview->SelectClass(ccl);
		Send(cPeSourceBrowser, cPeCLSelectClass, ccl);
		methods->ShowMethodsOf(cl);
		SafeDelete(fd);
	}
	if (reveal)
		SelectSourceLine(sameFile);
}

void PeSourceBrowser::SelectSourceLine(bool)
{
	int line;
	if (definition)
		line= ccl->GetDeclFileLine();
	else
		line= ccl->GetImplFileLine();

	RevealAndSelectLine(line);
}

void PeSourceBrowser::SetMode(bool what)
{
	SetClass(ccl, what);
}

void PeSourceBrowser::Spawn()
{
	SavedChanges();
	Spawn(gEtPeManager, ccl, definition);
}

void PeSourceBrowser::EditSelectedClass()
{
	char *classname= FirstWordOfSelection();
	Class *clp= gClassManager->Find(classname);
	if (clp)
		SetClass(clp,definition);
	else
		ShowAlert(eAlertNote, "No Metaclass found for \"%s\" ?", classname);
}

void PeSourceBrowser::ShowImplementors()
{
	PeMethodReference mr(0, 0, FirstWordOfSelection(), FALSE);
	implementors->ShowImplementorsOf(&mr, FALSE);
}

void PeSourceBrowser::InspectSomeInstance()
{
	Object *op= ccl->SomeMember();
	if (!op)
		op= ccl->SomeInstance();
	if (op)
		op->Inspect();
	else
		ShowAlert(eAlertNote, "No instances found");
}

MenuBar *PeSourceBrowser::DoMakeMenuBar()
{
	MenuBar *mb= Document::DoMakeMenuBar();

	//---- file menu
	Menu *m= new Menu("Classes");
	m->AppendItems( "Other",                  cEDITOTHER,
					"Superclass",             cEDITSUPER,
					"Spawn",                  cSPAWN,
					"-",
					"Inspect Some Instance",  cINSPECTSOME,
					"-",
					"Show in Hierarchy",      cSHOWHIERARCHY,
					"-",
					"Previous Class",         cGOBACK,
					"Empty Classes",          cEMPTYCLASSES,
					0);
	mb->AddMenu(m);

	m= new Menu("Methods");
	m->AppendItems( "Implementors",     cIMPLEMENTORS,
					"Overrides",        cOVERRIDES,
					"Inherited",        cINHERITED,
					"References",       cMETHREFERENCES,
					"-",
					"Filter...",        cFILTER,
					"Remove Filter",    cREMFILTER,
					0);
	mb->AddMenu(m);

	m= new Menu("Utilities");
	m->AppendItems( "Edit Selected Class",    cEDITSELECTED,
					"Go to Line...",          cGOTOLINE,
					"Reformat",               cREFORMATCODE,
					0);
	mb->AddMenu(m);

	FindChange::InstallChange(mb, textview);

	return mb;
}

void PeSourceBrowser::DoSetupMenu(Menu *menu)
{
	menu->ToggleItem(cEDITOTHER, definition, "Implementation", "Definition");

	if (ccl)
		menu->EnableItems(cEDITOTHER,
						cSPAWN,
						cSHOWHIERARCHY,
						cSHOWINHPATH,
						cINSPECTSOME,
						cUTILMENU,
						0);
						
	if (isCCode && text->IsKindOf(StyledText))
		menu->EnableItem(cREFORMATCODE);

	menu->EnableItems(cGOTOLINE, cEMPTYCLASSES, 0);

	if (ccl && ccl->Super())
		menu->EnableItem(cEDITSUPER);
	if (log->Size() > 1)
		menu->EnableItem(cGOBACK);
		
	menu->ToggleItem(cEMPTYCLASSES, listview->HideEmptyClasses(),
						"Show All Classes",
						"Hide Classes with no Instances");

	menu->ReplaceItem(cEDITSELECTED, "Selected Class/Method");
	if (!textview->Caret()) {
		char *q= FirstWordOfSelection();
		if (strlen(q)) {
			menu->ReplaceItem(cEDITSELECTED, form("Show \"%s\"", q));
			menu->EnableItem(cEDITSELECTED);
		}
	}

	if (methods->GetSelectedMethod() != 0) {
		menu->EnableItem(cIMPLEMENTORS);
		menu->EnableItem(cOVERRIDES);
		menu->EnableItem(cINHERITED);
	}
	menu->EnableItems(cFILTER, cREMFILTER, 0);

	Document::DoSetupMenu(menu);
}

char *PeSourceBrowser::FirstWordOfSelection()
{
	static byte buf[60];
	textview->SelectionAsString(buf, sizeof buf);
	for (byte *q= buf; *q; q++)
		if (Isinword(*q)) break;
	for (byte *p= q; *p; p++)
		if (!Isinword(*p)) break;
	*p= '\0';
	return (char*) q;
}

Command *PeSourceBrowser::DoMenuCommand(int cmd)
{
	char *q;
	PeMethodItem *mi= methods->GetSelectedMethod();

	switch (cmd) {

	case cFIND:
		FindChange::ShowChangeDialog(textview);
		break;

	case cEDITOTHER:
		SetMode(!definition);
		break;

	case cEDITSUPER:
		SetClass(ccl->Super(), definition);
		break;
		
	case cINSPECTSOME:
		InspectSomeInstance();
		break;
		
	case cEDITSELECTED:
		{
		GrShowWaitCursor wc;
		q= FirstWordOfSelection();
		if (gClassManager->Find(q))
			EditSelectedClass();
		else
			ShowImplementors();
		}
		break;
		
	case cEMPTYCLASSES:
		listview->ToggleHideEmpty();
		listview->SelectClass(ccl);
		break;

	case cGOBACK:
		log->RemoveAt(log->Size()-1);
		DoSetClass((Class*)log->Last(), definition, TRUE, TRUE);
		break;

	case cSPAWN:
		Spawn();
		break;
		
	case cSHOWHIERARCHY:
		gEtPeManager->Control(cPeSourceBrowser, cPeShowHierarchy, ccl);
		break;
		
	case cGOTOLINE:
		GotoLine(textview);
		break;
		
	case cREFORMATCODE:
		textview->FormatCode();
		textview->RepairAll();
		return gNoChanges;

	case cIMPLEMENTORS:
		Control(cPeSourceBrowser, cPeImplementors, mi->mrp);
		break;
		
	case cOVERRIDES:
		Control(cPeSourceBrowser, cPeOverrides, mi->mrp);
		break;
		
	case cINHERITED:
		Control(cPeSourceBrowser, cPeInherited, mi->mrp);
		break;
		
	case cFILTER:
		methods->Filter();
		break;

	case cREMFILTER:
		methods->RemoveFilter();
		break;

	default:
		break;
	}
	return Document::DoMenuCommand(cmd);
}

void PeSourceBrowser::CollectParts(Collection *col)
{
	EvtHandler::CollectParts(col);
}

void PeSourceBrowser::Spawn(Manager *m, Class *cp, bool decl, int at)
{
	PeSourceBrowser *sb= new PeSourceBrowser(m);
	sb->SetOnDismiss(eMgrClose);
	sb->Show();
	sb->DoSetClass(cp, decl, FALSE, at == -1);
	if (at != -1)
		sb->RevealAndSelectLine(at);
}

void PeSourceBrowser::ShowClass(Manager *m, Class *cp, bool decl, int at)
{
	if (browser == 0)
		browser= new PeSourceBrowser(m);
	browser->Show();
	browser->SetClass(cp, decl, at == -1);
	if (at != -1)
		browser->RevealAndSelectLine(at);
}

//---- PeSrcBrowserTextView ------------------------------------------------------

NewMetaImpl0(PeSrcBrowserTextView, CodeTextView);

PeSrcBrowserTextView::PeSrcBrowserTextView(EvtHandler *eh, Rectangle r,
					VObjectText *t) : CodeTextView(eh, r, t)
{
}

PrettyPrinter *PeSrcBrowserTextView::MakePrettyPrinter(Text *t,
		CharStyle *cs, CharStyle *fs, CharStyle *cds, CharStyle *ps)
{
	return new PeSrcBrowserPPrinter(this, t, cs, fs, cds, ps);
}

void PeSrcBrowserTextView::ShowMethod(PeMethodReference *mr)
{
	MarkList *mlp= GetMarkList();
	Iter next(mlp->MakeIterator());
	PeMethodMark *mp;
	bool found= FALSE;

	while (mp= (PeMethodMark*)next()) {
		if (strcmp(mp->className, mr->GetClass()->Name()) == 0 &&
				strcmp(mp->method, (char*)mr->Str()) == 0) {
			SetSelection(mp->Pos(),mp->End());
			found= TRUE;
			break;
		}
	}
	// no mark found, use position information stored in mr
	if (!found) {
		LineMark *lm= MarkAtLine(Math::Range(0, Lines()-1, mr->Line()-1));
		SetSelection(lm->Pos(),lm->End());
	}
	Rectangle r= SelectionRect();
	Scroll(cPartScrollAbs, Point(0,r.origin.y)+Point(0,-4));
}

//---- PeSrcBrowserPPrinter ------------------------------------------------------

PeSrcBrowserPPrinter::PeSrcBrowserPPrinter(PeSrcBrowserTextView *tv, Text *t,
		CharStyle *cs, CharStyle *fs, CharStyle *cds, CharStyle *ps)
					: PrettyPrinter(t, cs, fs, cds, ps)
{
	tvp= tv;
	tv->GetMarkList()->FreeAll();
}

void PeSrcBrowserPPrinter::Function(int line, int start, int end,
					const char *name, const char *classname)
{
	PrettyPrinter::Function(line, start, end, name, classname);
	if (classname)
		tvp->AddMark(new PeMethodMark(classname, name, start, end-start));
}

//---- PeMethodMark --------------------------------------------------------------

NewMetaImpl(PeMethodMark, Mark, (TP(className), TP(method)));

PeMethodMark::PeMethodMark(const char *c, const char *m, int p, int l) : Mark(p, l)
{
	className= strsave(c);
	method= strsave(m);
}

PeMethodMark::~PeMethodMark()
{
	SafeDelete(className);
	SafeDelete(method);
}

